﻿//////////////////////////////////////////////////////////////////////////////
//
// Copyright © 1998-2024 Glodon Company Limited.
//
// Licensed under the MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the “Software”),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//////////////////////////////////////////////////////////////////////////////
#include "GbmpPickLinkDocumentElementBeforeProxy.h"
#include "ILinkDocumentElement.h"
#include "ILinkElementProxy.h"
#include "IDocument.h"
#include "IPick.h"
#include "IPickResult.h"
#include "IPickEventArgs.h"
#include "IGraphicsNodeReference.h"
#include "ILinkDocumentType.h"
#include "GcmpBuiltInCategoryUniIdentities.h"
#include "EnableCompileWarning_The_LAST_IncludeInCpp.h"
using namespace gcmp;

void GbmpPickLinkDocumentBeforeProxyEventHandler::Process(IDocument* pDoc, 
    IPickEventArgs * pArgs, const Vector3d & pos) const
{
    std::vector<OwnerPtr<IPick>> candidatesLinkDocumentElement;
    std::vector<OwnerPtr<IPick>> candidatesLinkElementProxy;
    std::vector<OwnerPtr<IPick>> candidatesOthers;

    ElementId linkDocumentElementId;
    OwnerPtr<IPickResult> result = pArgs->GetPickCandidates().Clone();
    std::vector<OwnerPtr<IPick>>& candidates = result->GetAllPicks();
    FOR_EACH(candidate, candidates)
    {
        const GraphicsNodeReferenceOwnerPtrVector & gNodeReferences =
            candidate->GetAllGraphicsNodeReferences();
        FOR_EACH(gNodeReference, gNodeReferences)
        {
            ElementId elementId = gNodeReference->GetElementId();
            if (const ILinkDocumentElement* pLinkDocumentElement = 
                quick_cast<ILinkDocumentElement>(pDoc->GetElement(elementId)))
            {
                candidatesLinkDocumentElement.emplace_back(candidate->Clone());
            }
            else if (ILinkElementProxy* pLinkElementProxy =
                quick_cast<ILinkElementProxy>(pDoc->GetElement(elementId)))
            {
                if (!linkDocumentElementId.IsValid())
                {
                    const ILinkDocumentElement * pLinkDocumentElement =
                        pLinkElementProxy->GetLinkDocumentElement();
                    DBG_WARN_AND_CONTINUE_UNLESS(nullptr != pLinkDocumentElement,L"LinkElementProxy对应的LinkDocumentElement不存在？",L"GDMPLab",L"2024-03-30");
                    linkDocumentElementId = 
                        pLinkDocumentElement->GetElementId();
                }
                candidatesLinkElementProxy.emplace_back(candidate->Clone());
            }
            else
            {
                candidatesOthers.emplace_back(candidate->Clone());
            }
        }
    }

    // 有LinkElementProxy被选中，选择集一定会变化或顺序变化。
    if (!candidatesLinkElementProxy.empty())
    {
        candidates.clear();

        // 选中LinkElementProxy但没选中LinkDocumentElement时，
        // 需要加入LinkElementProxy对应的LinkDocumentElement。
        if (candidatesLinkDocumentElement.empty())
        {
            DBG_WARN_UNLESS(linkDocumentElementId.IsValid(), L"LinkDocumentElementId应该计算出来了",L"GDMPLab",L"2024-03-30");
            OwnerPtr<IGraphicsNodeReference> opGraphicsNodeReference =
                IGraphicsNodeReference::CreateGraphicsElementShapeReference(
                    linkDocumentElementId);
            OwnerPtr<IPick> opPick = IPick::Create();
            DBG_WARN_UNLESS(opPick->AddGraphicsNodeReference(*opGraphicsNodeReference.get()),L"向IPick中设置GraphicsNodeReference失败",L"GDMPLab",L"2024-03-30");
            result->AddPick(TransferOwnership(opPick));
        }

        FOR_EACH(candidate, candidatesLinkDocumentElement)
        {
            result->AddPick(TransferOwnership(candidate));
        }
        
        FOR_EACH(candidate, candidatesLinkElementProxy)
        {
            result->AddPick(TransferOwnership(candidate));
        }
        
        FOR_EACH(candidate, candidatesOthers)
        {
            result->AddPick(TransferOwnership(candidate));
        }

        pArgs->SetPickCandidates(*result);
    }
}

//////////////////////////////////////////////////////////////////////////
using LinkDocumentElementToProxyIndexMap = std::unordered_map<const ILinkDocumentElement*, std::tuple<bool, int, std::vector<int>>>;

namespace
{
    void CollectLinkDocumentElementToProxyIndexMap(
        const IDocument * pDoc,
        const OwnerPtr<IPick>& opPick,
        int index,
        LinkDocumentElementToProxyIndexMap& indices
    )
    {
        DBG_WARN_AND_RETURN_VOID_UNLESS(nullptr != pDoc, L"pDoc空?",L"GDMPLab",L"2024-03-30");

        const auto& nodeReferences = opPick->GetAllGraphicsNodeReferences();
        for (const auto& nodeRef : nodeReferences)
        {
            ElementId elemId = nodeRef->GetElementId();
            const IElement* elem = pDoc->GetElement(elemId);
            if (const auto linkDocElem = quick_cast<ILinkDocumentElement>(elem))
            {
                std::get<0>(indices[linkDocElem]) = true;
                std::get<1>(indices[linkDocElem]) = index;
            }
            else if (const auto linkElemProxy = quick_cast<ILinkElementProxy>(elem))
            {
                const auto linkDocElem = linkElemProxy->GetLinkDocumentElement();
                DBG_WARN_AND_RETURN_VOID_UNLESS(nullptr != linkDocElem, L"pLinkDocumentElement空?",L"GDMPLab",L"2024-03-30");
                std::get<2>(indices[linkDocElem]).push_back(index);
            }
            else
            {
                // 不用管
            }
        }
    }
}

bool GbmpPickProxyBeforeLinkDocumentEventHandler::DocumentHasLinkedEditableDocuments(const IDocument* pDoc)
{
    DBG_WARN_AND_RETURN_FALSE_UNLESS(nullptr != pDoc, L"文档指针为空?",L"GDMPLab",L"2024-03-30");
    std::vector<IElement*> linkDocElements = pDoc->GetElementsByCategory(BuiltInCategoryUniIdentities::BICU_LINK_DOCUMENT_ELEMENT);
    if (linkDocElements.empty()) return false;

    for (auto elem : linkDocElements)
    {
        const auto linkDocElem = quick_cast<ILinkDocumentElement>(elem);
        DBG_WARN_AND_RETURN_FALSE_UNLESS(linkDocElem != nullptr, L"按类别获取的对象不是链接文档对象？",L"GDMPLab",L"2024-03-30");
        const auto linkDocType = linkDocElem->GetLinkDocumentType();
        DBG_WARN_AND_RETURN_FALSE_UNLESS(linkDocType != nullptr, L"GetLinkDocumentType()返回空？",L"GDMPLab",L"2024-03-30");
        if (linkDocType->IsEditable())
        {
            return true;
        }
    }
    return false;
}

void GbmpPickProxyBeforeLinkDocumentEventHandler::On(IPickEventArgs* pArgs)
{
    DBG_WARN_AND_RETURN_VOID_UNLESS(nullptr != pArgs, L"拾取事件参数为空?",L"GDMPLab",L"2024-03-30");
    const IDocument* pDoc = pArgs->GetDocument();
    DBG_WARN_AND_RETURN_VOID_UNLESS(nullptr != pDoc, L"拾取事件参数中的文档指针为空?",L"GDMPLab",L"2024-03-30");

    // 此处行为是：没有可以编辑的参照文档时，不改行为（优先选择链接整体对象）。
    if (!DocumentHasLinkedEditableDocuments(pDoc)) return;

    // 以下是示例，具体行为由产品自行定义
    auto& pickCandidates = pArgs->GetPickCandidates();
    auto& allPicks = pickCandidates.GetAllPicks();
    const int nPicks = (int)allPicks.size();
    if (0 == nPicks) return;

    // 记录需要交换拾取顺序的位置
    LinkDocumentElementToProxyIndexMap indices;
    for (int idx = 0; idx < nPicks; ++idx)
    {
        CollectLinkDocumentElementToProxyIndexMap(pDoc, allPicks[idx], idx, indices);
    }

    // 计算需要交换的位置
    std::vector<OwnerPtr<IPick>> toBeAddedPicks;
    std::vector<std::pair<int, int>> toBeSwappedIndices{indices.size()};
    for (const auto& item : indices)
    {
        if (std::get<0>(item.second)) // 如果整体被选中
        {
            if (!std::get<2>(item.second).empty())
            {
                // 链接整体对象在最后一个代理对象之前，需要交换。确保链接整体对象在最后
                const int idxOfLinkDocElem = std::get<1>(item.second);
                const int idxOfLastProxyElem = std::get<2>(item.second).back();
                if (idxOfLinkDocElem < idxOfLastProxyElem)
                {
                    toBeSwappedIndices.emplace_back(idxOfLinkDocElem, idxOfLastProxyElem);
                }
            }
        }
        else // 如果整体没有被选中，添加。在最后了，不需要调整位置
        {
            const auto linkDocElem = item.first;
            DBG_WARN_AND_CONTINUE_UNLESS(linkDocElem, L"LinkDocumentElement为空？",L"GDMPLab",L"2024-03-30");
            OwnerPtr<IGraphicsNodeReference> opGraphicsNodeReference = IGraphicsNodeReference::CreateGraphicsElementShapeReference(linkDocElem->GetElementId());
            OwnerPtr<IPick> opPick = IPick::Create();
            DBG_WARN_AND_CONTINUE_UNLESS(opPick->AddGraphicsNodeReference(*opGraphicsNodeReference.get()), L"向IPick中设置GraphicsNodeReference失败",L"GDMPLab",L"2024-03-30");
            toBeAddedPicks.emplace_back(std::move(opPick));
        }
    }

    if (toBeAddedPicks.empty() && toBeSwappedIndices.empty()) return;

    // 克隆后交换
    OwnerPtr<IPickResult> clonedPickCandidates = pArgs->GetPickCandidates().Clone();
    auto& clonedAllPicks = clonedPickCandidates->GetAllPicks();
    for (const auto & item : toBeSwappedIndices)
    {
        std::swap(clonedAllPicks[item.first], clonedAllPicks[item.second]);
    }
    // 增加链接整体对象
    for (auto& item : toBeAddedPicks)
    {
        clonedAllPicks.emplace_back(std::move(item));
    }

    pArgs->SetPickCandidates(*clonedPickCandidates);
}
